/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

 /*Author: Jonas Karlsson 2011-10-26*/
 /*Some code is taken from the demo code at the Olimex website*/

#include <inttypes.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <avr/io.h>
#include "common.h"
#include "temperature_handler.h"
#include "delay_handler.h"
#include "eeprom_handler.h"
#include "i2cmaster.h"
#include "gsm_handler.h"
#include "uart_driver.h"

#define FOSC 7372800 //7,37MHz external osc

#define CMD_UNKNOWN     (0)
#define CMD_CLOSERELAY1 (1)
#define CMD_CLOSERELAY2 (2)
#define CMD_OPENRELAY1  (3)
#define CMD_OPENRELAY2  (4) 
#define CMD_GETSTATUS   (5)
#define CMD_GETBALANCE  (6)
#define CMD_SETMASTER   (7)
#define CMD_SETTEMPWARN (8)

#define CMD_STR_CLOSERELAY1     "CLOSERELAY1" 
#define CMD_STR_CLOSERELAY2     "CLOSERELAY2"
#define CMD_STR_OPENRELAY1      "OPENRELAY1"
#define CMD_STR_OPENRELAY2      "OPENRELAY2"
#define CMD_STR_GETSTATUS       "GETSTATUS"
#define CMD_STR_GETBALANCE      "SALDO"
#define CMD_STR_SETMASTER       "SETMASTER"
#define CMD_STR_SETTEMPWARN     "SETTEMPWARN"

#define TEMP_HYSTERESIS (0.5)

#define LED_CYCLE_COUNT (100000)
#define INT_TEMP_CYCLE_COUNT (100)

/*---- Global variables ----*/
/*TODO: rearrange code to make some global variables local instead.*/
static struct s_eepData eepData; /*Mirror of eeprom data*/
static double intTemperature = 0.0; /*Onboard temperature*/
static double extTemperature = 0.0; /*external temperature from tmp102*/

static char SwitchCommChannel_USB_MCU(void) 
{
  static char usbConnectedPrev = -2;
  char usbConnected; 

  if(PIND & 0b00100000)
    usbConnected = 1;
  else
    usbConnected = 0;

  if(usbConnected != usbConnectedPrev) {
    if(usbConnected == 1) {
      // deinitialize the UART
      UCSRA=0x00;
      UCSRB=0x00;
      UCSRC=0x00;
      UBRRH=0x00;               
      UBRRL=0x00;

      PORTA=0x00;                                       //RTS,CTS,DTR,DCD - inputs
      DDRA=0x00;        

      PORTD &= 0b10111100;      //RI,RTX,DTX- inputs 
      DDRD  &= 0b10111100;
                        
      // release FT232RL from reset
      PORTB &=0b11111011;
      DDRB &= 0b11111011;
    } else {
      // hold FT232RL in reset
      PORTB &=0b11111011;
      DDRB |= 0x04;     

      // initialize the UART
      PORTA &= 0b01011111;      //disable SLEEP mode with RTS=0; DTR =0
      DDRA |=  0b10100000;

      PORTD = 0b00000000;
      DDRD  = 0b10010010;

      UCSRA=0x00;
      UCSRB=0x98;
      UCSRC=0x86;
      UBRRH=0x00;       
      UBRRL=0x03;       

      // initialze serial buffer
      uartResetRxCounter();      
    }

    usbConnectedPrev = usbConnected;

    // ensure there are no spurious swithces of modes
    delay_ms(100);
  }

  return usbConnected;
}

static void ioinit(void)
{
  
  //No pull ups enabled on any port
  PORTA = 0x00;
  PORTB = 0x00;
  PORTC = 0x00;
  PORTD = 0x00;

  //Direction registers, 1 = output, 0 = input 
  DDRA = 0;
  DDRB = 0;
  DDRC=0xC0;
  DDRD=0x92;


  //PORTC = 0b00110000; //pullups on the I2C bus

  // Analog Comparator initialization
  // Analog Comparator: Off
  // Analog Comparator Input Capture by Timer/Counter 1: Off
  // Analog Comparator Output: Off
  ACSR=0x80;
  SFIOR=0x00;
        
  //Init Timer0 for delay_us
  TCCR0= (1<<CS01); //Set Prescaler to clk/8 : 1click = 0.5us(assume we are running at external 16MHz). CS01=1 
    
  /* Connect USB to GSM or MCU to GSM depending on USB cable presens*/
  SwitchCommChannel_USB_MCU();

  // Global enable interrupts
  asm volatile("sei");
}


static void toggle_LED (void)
{
  PORTD ^= 0x80;
}

static void closeRelay1 (void)
{
  PORTC|= 0x80;
  eepSaveRelay1Value(EEP_RELAY_CLOSED);
}

static void closeRelay2 (void)
{
  PORTC |= 0x40;
  eepSaveRelay2Value(EEP_RELAY_CLOSED);
}

static void openRelay1 (void)
{
  PORTC &= ~0x80;
  eepSaveRelay1Value(EEP_RELAY_OPEN);
}

static void openRelay2 (void)
{
  PORTC &= ~0x40;
  eepSaveRelay2Value(EEP_RELAY_OPEN);
}

static void buildStatusResponse(char* pStr)
{
  uint8_t i=0;

  if(PORTC & 0b10000000)
  {
    sprintf(pStr+i, "Relay1: Closed\r\n");
  }
  else {                
    sprintf(pStr+i,"Relay1: Open\r\n");
  }

  if(PORTC & 0b01000000)
  {
    sprintf(pStr+strlen(pStr),"Relay2: Closed\r\n");
  }
  else {                
    sprintf(pStr+strlen(pStr),"Relay2: Open\r\n");
  }

  sprintf(pStr+strlen(pStr), "Ext temp: %5.2f\r\n", extTemperature);

  sprintf(pStr+strlen(pStr), "Int temp: %5.2f\r\n", intTemperature);
  
  sprintf(pStr+strlen(pStr), "Temp warn level: %d\r\n", eepData.tempWarningLevel);
  sprintf(pStr+strlen(pStr), "Master no: %s\r\n", eepData.masterPhoneNo);  
}

static int8_t convertGsmTemp(char *pStr)
{
  int temp;
  uint8_t retVal;

  temp = atoi(pStr);

  if (temp != 0){
    retVal = (uint8_t) temp;
  } else {
    retVal = 0;
  }

  return retVal;
}

static void setNewMasterPhnNo(struct s_gsmData* pGsmData)
{
  eepSaveMasterPhoneNo(pGsmData->senderNumber);
  strncpy(eepData.masterPhoneNo, pGsmData->senderNumber, MAX_PHN_NO_LENGTH); 
}

static void setNewTempWarnLevel(struct s_gsmData* pGsmData)
{
  int retVal, temp;

  retVal = sscanf(pGsmData->content, "%*s %d", &temp);
  
  if (retVal == 1) 
  {
    if ((temp > -10) && (temp < 40)){
      eepSaveTempWarningLvl((int8_t) temp);  
      eepData.tempWarningLevel = (int8_t) temp;
    }
  }  
}

static int8_t extractSmsCommand(char* pCmdString)
{
  int8_t command;

  if (strncmp(pCmdString, CMD_STR_CLOSERELAY1, 
              strlen(CMD_STR_CLOSERELAY1)) == 0){
    command = CMD_CLOSERELAY1;
  } else if (strncmp(pCmdString, CMD_STR_CLOSERELAY2, 
                     strlen(CMD_STR_CLOSERELAY2)) == 0){
    command = CMD_CLOSERELAY2;
  } else if (strncmp(pCmdString, CMD_STR_OPENRELAY1, 
                     strlen(CMD_STR_OPENRELAY1)) == 0){
    command = CMD_OPENRELAY1;
  } else if (strncmp(pCmdString, CMD_STR_OPENRELAY2, 
                     strlen(CMD_STR_OPENRELAY2)) == 0){
    command = CMD_OPENRELAY2;
  } else if (strncmp(pCmdString, CMD_STR_GETSTATUS, 
                     strlen(CMD_STR_GETSTATUS)) == 0){
    command = CMD_GETSTATUS;
  } else if (strncmp(pCmdString, CMD_STR_GETBALANCE, 
                     strlen(CMD_STR_GETBALANCE)) == 0){
    command = CMD_GETBALANCE;
  } else if (strncmp(pCmdString, CMD_STR_SETMASTER, 
                     strlen(CMD_STR_SETMASTER)) == 0){
    command = CMD_SETMASTER;
  } else if (strncmp(pCmdString, CMD_STR_SETTEMPWARN, 
                     strlen(CMD_STR_SETTEMPWARN)) == 0){
    command = CMD_SETTEMPWARN;  
  } else {
    command = CMD_UNKNOWN;
  }

  return command;
}

static int8_t performSmsCommand(int8_t cmd, 
                                struct s_gsmData* pGsmData, 
                                struct s_replyData* pReplyData)
{
  int8_t retVal=RET_OK;

  switch(cmd){
  case CMD_UNKNOWN:
    /*Do nothing*/
    retVal = RET_NOK;
    break;
  case CMD_CLOSERELAY1:
    closeRelay1();
    buildStatusResponse(pReplyData->message);
    break;
  case CMD_CLOSERELAY2:
    closeRelay2();
    buildStatusResponse(pReplyData->message);
    break;
  case CMD_OPENRELAY1:
    openRelay1();
    buildStatusResponse(pReplyData->message);
    break;
  case CMD_OPENRELAY2:
    openRelay2();
    buildStatusResponse(pReplyData->message);
    break;
  case CMD_GETSTATUS:
    buildStatusResponse(pReplyData->message);
    break;
  case CMD_GETBALANCE:
    retVal = gsmSendBalanceRequest();
    if (retVal != RET_OK) goto FAIL;
  
    delay_ms(20000); //Give operator time to respond
  
    retVal = gsmDecodeIncomingMsg(pGsmData);
    if (retVal != RET_OK) goto FAIL;

    if (pGsmData->event == EVENT_CUSD) {
      strncpy(pReplyData->message, pGsmData->content, MAX_SMS_CONTENT_SIZE); 
    } else {
      retVal = RET_NOK;
    }
    break;
  case CMD_SETMASTER:
    setNewMasterPhnNo(pGsmData);
    buildStatusResponse(pReplyData->message);
    break;
  case CMD_SETTEMPWARN:
    setNewTempWarnLevel(pGsmData);
    buildStatusResponse(pReplyData->message);
    break;
  default:
    retVal = RET_NOK;
    //Do nothing
    break;
  }

  return retVal;
 FAIL:  
  return retVal;

}

void initRelays(struct s_eepData* pEepData)
{
  if (pEepData->relay1Value == EEP_RELAY_OPEN){
    openRelay1();
  } else {
    closeRelay1();
  }

  if (pEepData->relay2Value == EEP_RELAY_OPEN){
    openRelay2();
  } else {
    closeRelay2();
  }
}

int main (void)
{
  static struct s_gsmData gsmData;
  static struct s_replyData replyData;
        
  int8_t retVal, smsCommand;
  uint32_t LED_cycle=0;
  uint16_t intTempCycle=INT_TEMP_CYCLE_COUNT;
  int8_t prevWarnActive = 0;

  ioinit(); //Boot up defaults
  i2c_init();

  delay_ms(200);

  /*act as PowerKey - it should be hold down about 1-2 seconds*/
  DDRA  = 0b00000100;//PA2 out in zero
  delay_ms(2000);
  PORTA &=0b11111011;
  DDRA  &=0b11111011;//PA2 back input

  /*Wait for GSM module to send "Call ready"*/
  while( gsmWaitForCallReady() != RET_OK ) {
    SwitchCommChannel_USB_MCU();
  }

  gsmConfig();

  /* Get config data from eeprom (Relay positions and such)*/
  retVal = eepGetStoredData(&eepData);
  if (retVal != EEP_OK){
    /*Didn't find valid config data in eeprom. Write default to eeprom.*/
    eepInitEep(&eepData);
  }

  /*Set relays according to eeprom setting.*/
  initRelays(&eepData);

  while(1)
    {
        
      // Blink LED every 100000 lap to see that software main loop is alive.
      //TODO: Use a timer instead of a loop counter.
      if (++LED_cycle >= LED_CYCLE_COUNT) {
        toggle_LED();
        LED_cycle = 0;

        /*Perform temperature measurement on external i2c sensor*/
        extTemperature = extTempRead();

        /*Send temperature measurement to GSM module. */
        /*Response will be handled in the receive loop below */
        /*Only measure internal.temp appr. every 2 min*/
        if (++intTempCycle >= INT_TEMP_CYCLE_COUNT){
            intTemperature = -300.0;    //Force to impossible value to be able to 
                                        //see if measurement fails. This value will remin
                                        //until next successful measurement
            gsmSendTemperatureRequest(); 
            intTempCycle = 0;                        
        }

        /*Handle low temperature surveillance*/
        if (!prevWarnActive){        
          /* Only send warning sms one time when temperature goes under warning level*/
          if ((int8_t) extTemperature < eepData.tempWarningLevel){
            //Send sms
            strncpy(replyData.destNumber, eepData.masterPhoneNo, MAX_PHN_NO_LENGTH);
            buildStatusResponse(replyData.message);
            delay_ms(2000);
            gsmSendSms(&replyData); 
            prevWarnActive = 1;
          }
        } else {
            if ((int8_t) extTemperature > 
                (eepData.tempWarningLevel + TEMP_HYSTERESIS)){
            //Send sms to indicate that temp is ok again
            strncpy(replyData.destNumber, eepData.masterPhoneNo, MAX_PHN_NO_LENGTH);
            buildStatusResponse(replyData.message); 
            delay_ms(2000);
            gsmSendSms(&replyData);
            prevWarnActive = 0;
          }
        }
      }

      /* Connect USB to GSM or MCU to GSM depending on USB cable presens*/
      SwitchCommChannel_USB_MCU(); 

      /* if data has been received on UART*/
      if (uartGetRxCounter() != 0){

        retVal = gsmDecodeIncomingMsg(&gsmData);
      
        if (retVal == RET_OK){
          switch (gsmData.event){
          case EVENT_CMTI:
            /*An SMS has arrived. Send a request for the contents of it.*/
            gsmSendSmsContentRequest(gsmData.smsIndex);
            break;
          case EVENT_CMTE:
            /*Decode gsm module temperature response*/
            intTemperature = convertGsmTemp(gsmData.content);
            break;
          case EVENT_CMGR:
            /*Save phone number to respond to*/
            strncpy(replyData.destNumber, gsmData.senderNumber, MAX_SMS_CONTENT_SIZE);
          
            /*Delete sms in gsm module*/         
            gsmDeleteSms(gsmData.smsIndex);
                                  
            /* Extract command to perform from incoming SMS text*/
            smsCommand = extractSmsCommand(gsmData.content);
            
            /* Perform the requested command*/
            retVal = performSmsCommand(smsCommand, &gsmData, &replyData);
          
            /*Send sms response only if command has been successful*/
            if (retVal == RET_OK){
              gsmSendSms(&replyData);
            }
            break;
          case EVENT_CUSD:
            /*Ignore! Not expected here!*/
            break;
          default:
            //FUBAR!
            break;
          }
        }
      }
    }
    
  return(0);
}

